/* -------------------------------------------------------------------*/

function vec4add(r, a, b) {
	r.x = a.x + b.x;
	r.y = a.y + b.y;
	r.z = a.z + b.z;
	r.w = a.w + b.w;
	return r;
}
function vec4sub(r, a, b) {
	r.x = a.x - b.x;
	r.y = a.y - b.y;
	r.z = a.z - b.z;
	r.w = a.w - b.w;
	return r;
}
function vec4addscalar(r, a, b) {
	r.x = a.x + b;
	r.y = a.y + b;
	r.z = a.z + b;
	r.w = a.w + b;
	return r;
}
function vec4subscalar(r, a, b) {
	r.x = a.x - b;
	r.y = a.y - b;
	r.z = a.z - b;
	r.w = a.w - b;
	return r;
}
function vec4mul(r, a, b) {
	r.x = a.x * b.x;
	r.y = a.y * b.y;
	r.z = a.z * b.z;
	r.w = a.w * b.w;
	return r;
}
function vec4mad(r, v, m, a) {
	r.x = v.x * m.x + a.x;
	r.y = v.y * m.y + a.y;
	r.z = v.z * m.z + a.z;
	r.w = v.w * m.w + a.w;
	return r;
}
function vec4div(r, a, b) {
	r.x = a.x / b.x;
	r.y = a.y / b.y;
	r.z = a.z / b.z;
	r.w = a.w / b.w;
	return r;
}
function vec4scale(r, a, s) {
	r.x = a.x * s;
	r.y = a.y * s;
	r.z = a.z * s;
	r.w = a.w * s;
	return r;
}
function vec4floor(r, a) {
	r.x = Math.floor(a.x);
	r.y = Math.floor(a.y);
	r.z = Math.floor(a.z);
	r.w = Math.floor(a.w);
	return r;
}
function vec4frac(r, a) {
	r.x = a.x - Math.floor(a.x);
	r.y = a.y - Math.floor(a.y);
	r.z = a.z - Math.floor(a.z);
	r.w = a.w - Math.floor(a.w);
	return r;
}
function vec4dot(a, b) {
	return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}
function vec4normalize(r, v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w);
	if(l==0) {
		r.x = 0.0;
		r.y = 0.0;
		r.z = 0.0;
		r.w = 0.0;
	} else {
		r.x = v.x/l;
		r.y = v.y/l;
		r.z = v.z/l;
		r.w = v.w/l;
	} 
	return r;
}
function vec4abs(r, v) {
	r.x = Math.abs(v.x);
	r.y = Math.abs(v.y);
	r.z = Math.abs(v.z);
	r.w = Math.abs(v.w);
	return r;
}
function vec4mix(r, a, b, t) {
	r.x = a.x*(1.0-t) + b.x*t;
	r.y = a.y*(1.0-t) + b.y*t;
	r.z = a.z*(1.0-t) + b.z*t;
	r.w = a.w*(1.0-t) + b.w*t;
	return r;
}

function vec3(x, y, z) { 
	this.x = x;
	this.y = y;
	this.z = z;
}

/*------------------------------------------------------------------------------------------*/
function vec4(x, y, z, w) {
	if(arguments.length==4) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = w;
	} else {
		this.x = this.y = this.z = this.w = 0.0;
	}

	this.normalize = function() {
		var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w);
		if(l==0) {
			this.x = 0.0;
			this.y = 0.0;
			this.z = 0.0;
			this.w = 0.0;
		} else {
			this.x = v.x/l;
			this.y = v.y/l;
			this.z = v.z/l;
			this.w = v.w/l;
		} 
		return this;
	}
	this.length = function() {
		return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w);
	}
	this.lengthsq = function() {
		return this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w;
	}
	this.abs = function() {
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);
		this.z = Math.abs(this.z);
		this.w = Math.abs(this.w);
		return this;
	}
	this.addvec = function(v) {
		this.x += v.x;
		this.y += v.y;
		this.z += v.z;
		this.w += v.w;
		return this;
	}
	this.add = function(v) {
		this.x += v;
		this.y += v;
		this.z += v;
		this.w += v;
		return this;
	}
	this.subvec = function(v) {
		this.x -= v.x;
		this.y -= v.y;
		this.z -= v.z;
		this.w -= v.w;
		return this;
	}
	this.sub = function(v) {
		this.x -= v;
		this.y -= v;
		this.z -= v;
		this.w -= v;
		return this;
	}
	this.mulvec = function(v) {
		this.x *= v.x;
		this.y *= v.y;
		this.z *= v.z;
		this.w *= v.w;
		return this;
	}
	this.mad = function(m, a) {
		this.x = this.x * m.x + a.x;
		this.y = this.y * m.y + a.y;
		this.z = this.z * m.z + a.z;
		this.w = this.w * m.w + a.w;
	}
	this.madscalar = function(m, a) {
		this.x = this.x * m + a;
		this.y = this.y * m + a;
		this.z = this.z * m + a;
		this.w = this.w * m + a;
	}
	this.scale = function(s) {
		this.x *= s;
		this.y *= s;
		this.z *= s;
		this.w *= s;
		return this;
	}
	this.dot = function(v) {
		return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w;
	}
	this.floor = function() {
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		this.z = Math.floor(this.z);
		this.w = Math.floor(this.w);
		return this;
	}
	this.frac = function() {
		this.x -= Math.floor(this.x);
		this.y -= Math.floor(this.y);
		this.z -= Math.floor(this.z);
		this.w -= Math.floor(this.w);
		return this;
	}
	this.copy = function() {
		return new vec4(this.x, this.y, this.z, this.w);
	}	
}
function vec3add(r, a, b) {
	r.x = a.x + b.x;
	r.y = a.y + b.y;
	r.z = a.z + b.z;
	return r;
}
function vec3sub(r, a, b) {
	r.x = a.x - b.x;
	r.y = a.y - b.y;
	r.z = a.z - b.z;
	return r;
}
function vec3addscalar(r, a, b) {
	r.x = a.x + b;
	r.y = a.y + b;
	r.z = a.z + b;
	return r;
}
function vec3subscalar(r, a, b) {
	r.x = a.x - b;
	r.y = a.y - b;
	r.z = a.z - b;
	return r;
}
function vec3mul(r, a, b) {
	r.x = a.x * b.x;
	r.y = a.y * b.y;
	r.z = a.z * b.z;
	return r;
}
function vec4mad(r, v, m, a) {
	r.x = v.x * m.x + a.x;
	r.y = v.y * m.y + a.y;
	r.z = v.z * m.z + a.z;
	return r;
}
function vec3div(r, a, b) {
	r.x = a.x / b.x;
	r.y = a.y / b.y;
	r.z = a.z / b.z;
	return r;
}
function vec3scale(r, a, s) {
	r.x = a.x * s;
	r.y = a.y * s;
	r.z = a.z * s;
	return r;
}
function vec3floor(r, a) {
	r.x = Math.floor(a.x);
	r.y = Math.floor(a.y);
	r.z = Math.floor(a.z);
	return r;
}
function vec3frac(r, a) {
	r.x = a.x - Math.floor(a.x);
	r.y = a.y - Math.floor(a.y);
	r.z = a.z - Math.floor(a.z);
	return r;
}
function vec3dot(a, b) {
	return a.x*b.x + a.y*b.y + a.z*b.z;
}
function vec3normalize(r, v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
	if(l==0) {
		r.x = 0.0;
		r.y = 0.0;
		r.z = 0.0;
	} else {
		r.x = v.x/l;
		r.y = v.y/l;
		r.z = v.z/l;
	} 
	return r;
}
function vec3abs(r, v) {
	r.x = Math.abs(v.x);
	r.y = Math.abs(v.y);
	r.z = Math.abs(v.z);
	return r;
}
function vec3mix(r, a, b, t) {
	r.x = a.x*(1.0-t) + b.x*t;
	r.y = a.y*(1.0-t) + b.y*t;
	r.z = a.z*(1.0-t) + b.z*t;
	return r;
}
function vec3(x, y, z) { 
	this.x = x;
	this.y = y;
	this.z = z;
}

/*------------------------------------------------------------------------------------------*/

function vec2add(r, a, b) {
	r.x = a.x + b.x;
	r.y = a.y + b.y;
	return r;
}
function vec2sub(r, a, b) {
	r.x = a.x - b.x;
	r.y = a.y - b.y;
	return r;
}
function vec2addscalar(r, a, b) {
	r.x = a.x + b;
	r.y = a.y + b;
	return r;
}
function vec2subscalar(r, a, b) {
	r.x = a.x - b;
	r.y = a.y - b;
	return r;
}
function vec2mul(r, a, b) {
	r.x = a.x * b.x;
	r.y = a.y * b.y;
	return r;
}
function vec2div(r, a, b) {
	r.x = a.x / b.x;
	r.y = a.y / b.y;
	return r;
}
function vec2scale(r, a, s) {
	r.x = a.x * s;
	r.y = a.y * s;
	return r;
}
function vec2mad(r, v, m, a) {
	r.x = v.x * m.x + a.x;
	r.y = v.y * m.y + a.y;
	return r;
}
function vec2floor(r, a) {
	r.x = Math.floor(a.x);
	r.y = Math.floor(a.y);
	return r;
}
function vec2frac(r, a) {
	r.x = a.x - Math.floor(a.x);
	r.y = a.y - Math.floor(a.y);
	return r;
}
function vec2dot(a, b) {
	return a.x*b.x + a.y*b.y;
}
function vec2normalize(r, v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y);
	if(l==0) {
		r.x = 0.0;
		r.y = 0.0;
	} else {
		r.x = v.x/l;
		r.y = v.y/l;
	} 
	return r;
}
function vec2abs(r, v) {
	r.x = Math.abs(v.x);
	r.y = Math.abs(v.y);
	return r;
}
function vec2mix(r, a, b, t) {
	r.x = a.x*(1.0-t) + b.x*t;
	r.y = a.y*(1.0-t) + b.y*t;
	return r;
}


function vec2(x, y) { 
	if(arguments.length==2) {
		this.x = x;
		this.y = y;
	} else {
		this.x = this.y = 0.0;
	}

	this.normalize = function() {
		var l = Math.sqrt(v.x*v.x + v.y*v.y);
		if(l==0) {
			this.x = 0.0;
			this.y = 0.0;
		} else {
			this.x = v.x/l;
			this.y = v.y/l;
		} 
		return this;
	}
	this.length = function() {
		return Math.sqrt(this.x*this.x + this.y*this.y);
	}
	this.lengthsq = function() {
		return this.x*this.x + this.y*this.y; 
	}
	this.abs = function() {
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);
		return this;
	}
	this.addvec = function(v) {
		this.x += v.x;
		this.y += v.y;
		return this;
	}
	this.subvec = function(v) {
		this.x -= v.x;
		this.y -= v.y;
		return this;
	}
	this.mulvec = function(v) {
		this.x *= v.x;
		this.y *= v.y;
		return this;
	}
	this.scale = function(s) {
		this.x *= s;
		this.y *= s;
		return this;
	}
	this.mad = function(m, a) {
		this.x = this.x * m.x + a.x;
		this.y = this.y * m.y + a.y;
		return this;
	}
	this.madscalar = function(m, a) {
		this.x = this.x * m + a;
		this.y = this.y * m + a;
		return this;
	}
	this.dot = function(v) {
		return this.x*v.x + this.y*v.y;
	}
	this.copy = function() {
		return new vec2(this.x, this.y);
	}	
}

function normalize(v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
	if(l==0)
		return new vec3(0,0,0);
	else
		return new vec3(v.x/l, v.y/l, v.z/l); 
}

function normalize2(out, v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
	if(l==0)
		 out.x = out.y = out.z = 0.0;
	else
		out.x = v.x/l; out.y = v.y/l; out.z = v.z/l; 
}
function dot(u, v) {
	return v.x*u.x + v.y*u.y + v.z*u.z;
}
function cross(u, v) {
        return new vec3(u.y * v.z - v.y * u.z, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x);
	
}

function vec3add(a, b) {
	return new vec3(a.x+b.x, a.y+b.y, a.z+b.z);
}
function vec3sub(a, b) {
	return new vec3(a.x-b.x, a.y-b.y, a.z-b.z);
}
function vec3normalize(out, v) {
	var l = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
	if(l==0)
		 out.x = out.y = out.z = 0.0;
	else
		out.x = v.x/l; out.y = v.y/l; out.z = v.z/l;
	return out;
}

/* -------------------------------------------------------------------*/
function mat4() {
   	this.buf = new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT*16);
	this.elem = new Float32Array(this.buf);
	
	// define methods
	this.translate = function translate(x,y,z) {
		this.elem[12] += (x * this.elem[0] + y * this.elem[4] + z * this.elem[8]);
	        this.elem[13] += (x * this.elem[1] + y * this.elem[5] + z * this.elem[9]);
        	this.elem[14] += (x * this.elem[2] + y * this.elem[6] + z * this.elem[10]);
        	this.elem[15] += (x * this.elem[3] + y * this.elem[7] + z * this.elem[11]);
	}
	this.pretranslate = function pretranslate(x,y,z) {
		this.elem[12] += x;
	        this.elem[13] += y;
        	this.elem[14] += z;
        	this.elem[15] += 0;
	}
	this.raw = function() {
		return this.elem;
	}

	if(arguments.length==16) {
		for(var i=0;i<16;++i) {
			this.elem[i] = arguments[i];
		}
	} else {
		this.elem[0] = 1; 
		this.elem[1] = 0; 
		this.elem[2] = 0; 
		this.elem[3] = 0;

		this.elem[4] = 0; 
		this.elem[5] = 1; 
		this.elem[6] = 0; 
		this.elem[7] = 0;

		this.elem[8] = 0; 
		this.elem[9] = 0; 
		this.elem[10] = 1; 
		this.elem[11] = 0;

		this.elem[12] = 0; 
		this.elem[13] = 0; 
		this.elem[14] = 0; 
		this.elem[15] = 1;
	}
}

function mat4mul(m, n){
        var mat = new mat4();
        for (var k = 0; k < 4; k++){
                for (var j = 0; j < 4; j++){
                        mat.elem[k + 4*j] = 0;
                        for (var i = 0; i < 4; i++){
                                mat.elem[k + 4*j] += m.elem[k + 4*i] * n.elem[i + 4*j];
                        }
                }
        }
        return mat;
}

function rotationZ4(angle) {
        var cosA = Math.cos(angle);
	var sinA = Math.sin(angle);

        return new mat4(
                cosA, sinA, 0, 0,
                -sinA,  cosA, 0, 0,
                0,     0,    1, 0,
                0,     0,    0, 1);
}

function lookAt( from, at) {
        var z = normalize(vec3sub(at, from));
        var x = normalize(new vec3(z.z, 0, -z.x));
        var y = normalize(cross(z, x));
        var m = new mat4(
		x.x, y.x, z.x, 0,
		x.y, y.y, z.y, 0,
		x.z, y.z, z.z, 0,
		0,   0,   0,   1);
        m.translate(-from.x, -from.y, -from.z);

        return m;
}

function lookAt2(c, e, u) {
        var l = normalize(vec3sub(c, e));
        var s = normalize(cross(l, u));
        var u = normalize(cross(s, l));
        var m = new mat4(
		s.x, u.x, -l.x, 0,
		s.y, u.y, -l.y, 0,
		s.z, u.z, -l.z, 0,
		dot(e, s), dot(e,u), dot(e,l), 1);
        return m;
}
function projectionMatrix(left, right, bottom, top,near, far)
{
	var x,y,a,b,c,d;
	x = 2.0*near / (right - left);
	y = 2.0*near / (top - bottom);
	a = (right + left) / (right - left);
	b = (top + bottom) / (top - bottom);
	c = -(far + near) / (far - near);
	d = -(2.0*far*near) / (far - near);

	var m = new mat4(
		x, 0, 0, 0,
		0, y, 0, 0,
		a, b, c, -1,
		0, 0, d, 0);

	return m;
}
